using System;
using System.Diagnostics;
using System.Globalization;

using Atomic.Libraries.Mathematics;

namespace Atomic.Structures
{
	/// <summary>
	/// Representing a set of lattice vectors; the lattice vectors themselves must be a basis.
	/// </summary>
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class Lattice : Basis
	{
		private Lattice reciprocalLattice;

		public Lattice(Position a, Position b, Position c)
			: base(a, b, c)
		{
			// http://en.wikipedia.org/wiki/Bravais_lattice#Bravais_lattices_in_3_dimensions
			Volume = Math.Abs(SpaceVector.Dot(a.PhysicalCoordinates, SpaceVector.Cross(b.PhysicalCoordinates, c.PhysicalCoordinates)));
		}

		public Lattice(Basis basis, SpaceVector a, SpaceVector b, SpaceVector c)
			: this(new Position(basis, a), new Position(basis, b), new Position(basis, c))
		{
		}

		public static Lattice CreateSimpleCubicLattice(double a)
		{
			return CreateSimpleCubicLattice(Basis.StandardBasis, a, SpaceMatrix.Identity);
		}

		public static Lattice CreateSimpleCubicLattice(Basis basis, double a, SpaceMatrix transform)
		{
			return new Lattice(basis.Decompose(new Position(transform * new SpaceVector(1.0, 0.0, 0.0) * a)), basis.Decompose(new Position(transform * new SpaceVector(0.0, 1.0, 0.0) * a)), basis.Decompose(new Position(transform * new SpaceVector(0.0, 0.0, 1.0) * a)));
		}

		public static Lattice CreateHexagonalLattice(double a, double c)
		{
			return CreateHexagonalLattice(Basis.StandardBasis, a, c, SpaceMatrix.Identity);
		}

		public static Lattice CreateHexagonalLattice(Basis basis, double a, double c, SpaceMatrix transform)
		{
			return new Lattice(basis.Decompose(new Position(transform * new SpaceVector(1.0, 0.0, 0.0) * a)), basis.Decompose(new Position(transform * new SpaceVector(0.5, 0.86602540378443865, 0.0) * a)), basis.Decompose(new Position(transform * new SpaceVector(0.0, 0.0, 1.0) * c)));
		}

		public static Lattice Parse(SpaceVector a, SpaceVector b, SpaceVector c)
		{
			return Parse(Basis.StandardBasis, a, b, c);
		}

		public static Lattice Parse(Basis basis, SpaceVector a, SpaceVector b, SpaceVector c)
		{
			// Try determine the Bravais lattice. It should in principle to possible identify it as on the 14 different Bravais lattices.
			/*Lattice lattice;

			if (SimpleCubicLattice.TryParse(basis, a, b, c, out lattice))
			{
				return lattice;
			}

			if (FaceCenteredCubicLattice.TryParse(basis, a, b, c, out lattice))
			{
				return lattice;
			}
				
			if (BodyCenteredCubicLattice.TryParse(basis, a, b, c, out lattice))
			{
				return lattice;
			}

			if (HexagonalLattice.TryParse(basis, a, b, c, out lattice))
			{
				return lattice;
			}*/

			// Fall back to the generic representation of the lattice.
			return new Lattice(basis, a, b, c);
		}

		protected static void CheckOrthogonal(SpaceMatrix transform)
		{
			double d = SpaceMatrix.Determinant(transform);
			if (Math.Abs(Math.Abs(d) - 1.0) > 1.0e-10)
			{
				throw new ArgumentException("Matrix transform must be an orthogonal matrix.");
			}
		}

		protected virtual Lattice CreateReciprocalLattice()
		{
			SpaceVector a0 = A.PhysicalCoordinates;
			SpaceVector b0 = B.PhysicalCoordinates;
			SpaceVector c0 = C.PhysicalCoordinates;

			SpaceVector a = 2.0 * Math.PI * SpaceVector.Cross(b0, c0) / SpaceVector.Dot(a0, SpaceVector.Cross(b0, c0));
			SpaceVector b = 2.0 * Math.PI * SpaceVector.Cross(c0, a0) / SpaceVector.Dot(b0, SpaceVector.Cross(c0, a0));
			SpaceVector c = 2.0 * Math.PI * SpaceVector.Cross(a0, b0) / SpaceVector.Dot(c0, SpaceVector.Cross(a0, b0));

			// Use different basis?
			return new Lattice(Basis.StandardBasis, a, b, c);
		}

		/// <summary>
		/// Volume spanned by the lattice vectors.
		/// </summary>
		public double Volume
		{
			get;
			private set;
		}

		public Lattice ReciprocalLattice
		{
			get
			{
				if (reciprocalLattice == null)
				{
					reciprocalLattice = CreateReciprocalLattice();
				}

				return reciprocalLattice;
			}
		}

		private string DebuggerDisplay
		{
			get
			{
				return string.Format(CultureInfo.InvariantCulture, "A = ({0:f4}, {1:f4}, {2:f4}), B = ({3:f4}, {4:f4}, {5:f4}), C = ({6:f4}, {7:f4}, {8:f4})",
					A.PhysicalCoordinates.X, A.PhysicalCoordinates.Y, A.PhysicalCoordinates.Z, 
					B.PhysicalCoordinates.X, B.PhysicalCoordinates.Y, B.PhysicalCoordinates.Z, 
					C.PhysicalCoordinates.X, C.PhysicalCoordinates.Y, C.PhysicalCoordinates.Z);
			}
		}
	}
}
